package com.mc.service.did.impl;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.mc.config.SpringUtils;
import com.mc.pojo.po.LeafIdPO;

/**
 * 号段管理
 */
public class SegmentManage {

	private static IdDbLoader idDbLoader;

	// 业务类型 如：order、goods、user、dbid
	private final String bizTag;
	// 当前使用号段
	private volatile Segment current;
	// 备用号段
	private volatile Segment backup = null;
	// 重新填充
	private volatile boolean isRefilling = false;
	// 本地锁
	private final Lock localLock = new ReentrantLock();;
	// 步长
	private final int step;
	// 临界值
	private final int criticalValue;
	// ID分配计数器
	private final LongAdder allocCounter = new LongAdder();

	public SegmentManage(String bizTag) {
		this.bizTag = bizTag;
		this.current = loadSegmentFromDB();
		this.backup = null;
		this.step = (int) (current.getEnd() - current.getStart());
		this.criticalValue = (int) (step * 0.9);
	}

	static {
		idDbLoader = SpringUtils.getBean(IdDbLoader.class);
	}

	public long nextId() {
		allocCounter.increment();
		long id = current.next();
		if (allocCounter.sum() > criticalValue && !isRefilling && null == backup) {
			// 填充数据
			triggerAsyncRefill();
		}
		if (id > current.getEnd()) {
			return handleExhausted();
		}
		return id;
	}

	private void triggerAsyncRefill() {
		isRefilling = true;
		CompletableFuture.runAsync(() -> {
			localLock.lock();
			if(false == isRefilling) {
				localLock.unlock();
			}else {
				try {
					backup = loadSegmentFromDB();
				} finally {
					isRefilling = false;
					localLock.unlock();
				}
			}
		});
	}

	private long handleExhausted() {
		localLock.lock();
		try {
			// 双重检查
			long id = current.next();
			if (id <= current.getEnd()) {
				return id;
			}
			// 切换
			current = backup;
			backup = null;
			return current.next();
		} finally {
			localLock.unlock();
		}

	}

	private Segment loadSegmentFromDB() {
		LeafIdPO po = idDbLoader.handleDb(bizTag);
		return new Segment(po.getMaxId() - po.getStep() + 1, po.getMaxId());
	}
}
